// implement fork from user space

#include <inc/string.h>
#include <inc/lib.h>

// PTE_COW marks copy-on-write page table entries.
// It is one of the bits explicitly allocated to user processes (PTE_AVAIL).
#define PTE_COW		0x800

//
// Custom page fault handler - if faulting page is copy-on-write,
// map in our own private writable copy.
//
static void
pgfault(struct UTrapframe *utf)
{
	void *addr = (void *) utf->utf_fault_va;
	uint32_t err = utf->utf_err;
	int r;

	// Check that the faulting access was (1) a write, and (2) to a
	// copy-on-write page.  If not, panic.
	// Hint:
	//   Use the read-only page table mappings at uvpt
	//   (see <inc/memlayout.h>).

	// LAB 4: Your code here.

	// Allocate a new page, map it at a temporary location (PFTEMP),
	// copy the data from the old page to the new page, then move the new
	// page to the old page's address.
	// Hint:
	//   You should make three system calls.

	// LAB 4: Your code here.
	//原来如此
	/*
	将父进程空间中所有可以写的页表的部分全部标记为可读且COW【因此读取的时候不会进入pgfault，但是写的时候会触发pgfault】。
	而当父进程或者子进程任意一个发生了写的时候，因为页表现在都是不可写的，所以会触发异常，进入到我们设定的page fault处理例程，
	当检测到是对COW页的写操作的情况下，就可以将要写入的页的内容全部拷贝一份，重新映射。
	*/
    if (!((err & FEC_WR) && (uvpd[PDX(addr)] & PTE_P) && 
			(uvpt[PGNUM(addr)] & PTE_P) && (uvpt[PGNUM(addr)] & PTE_COW)))
                panic("pgfault: it's not writable or attempt to access a non-cow page!");
	//为该进程分配新的页面。
	envid_t envid=sys_getenvid();
	r=sys_page_alloc(envid,(void*)PFTEMP,PTE_P|PTE_U|PTE_W);
	if(r<0){
		 panic("pgfault: page allocation failed %e", r);
	}
	//解开了我的疑惑，为什么随着fork页空间会逐渐发生偏移。
	//迁移数据过去。
	addr=ROUNDDOWN(addr,PGSIZE);
	//拷贝内容。
	memmove(PFTEMP,addr,PGSIZE);
	//解除和addr的映射关系。
	r=sys_page_unmap(envid,(void*)addr);
	if(r<0)
	panic("pgfault: page unmap failed %e", r);
	r=sys_page_map(envid,PFTEMP,envid,addr,PTE_P|PTE_U|PTE_W);
	if(r<0)
	panic("pgfault: page map failed %e", r);
	r=sys_page_unmap(envid,PFTEMP);
	if(r<0)
	panic("pgfault: page unmap failed %e", r);
}

//
// Map our virtual page pn (address pn*PGSIZE) into the target envid
// at the same virtual address.  If the page is writable or copy-on-write,
// the new mapping must be created copy-on-write, and then our mapping must be
// marked copy-on-write as well.  (Exercise: Why do we need to mark ours
// copy-on-write again if it was already copy-on-write at the beginning of
// this function?)
//
// Returns: 0 on success, < 0 on error.
// It is also OK to panic on error.
//
static int
duppage(envid_t envid, unsigned pn)
{
	int r;

	// LAB 4: Your code here.
	    void *addr;
        pte_t pte;
        int perm;

        addr = (void *)((uint32_t)pn * PGSIZE);
        pte = uvpt[pn];
    	//如果这个页是可写的页或者本身是写时拷贝页【还没有拷贝还是COW权限，比如fork后继续fork，那么子子进程还是要对子进程的COW页进行COW权限的设置】，那么就要标记COW
		if(pte&PTE_SHARE){
			sys_page_map(0, addr, envid, addr, PTE_SYSCALL);
		}
        else if((pte & PTE_COW) || (pte & PTE_W))
        {
            r = sys_page_map(0, addr, envid, addr, PTE_COW | PTE_U | PTE_P);
            if(r < 0)
                panic("duppage: sys_page_map fail\n");
            r = sys_page_map(0, addr, 0, addr, PTE_COW | PTE_U | PTE_P);
            if(r < 0)
                panic("duppage: sys_page_map fail\n");
        }
        else	//如果这个页就是普通的可读页，那么就依旧标记为可读页就好了。不需要写时拷贝，直接在父子进程之间共享。
        {
            r = sys_page_map(0, addr, envid, addr, PTE_U | PTE_P);
            if(r < 0)
                panic("duppage: sys_page_map fail\n");
        }
	return 0;
}

//
// User-level fork with copy-on-write.
// Set up our page fault handler appropriately.
// Create a child.
// Copy our address space and page fault handler setup to the child.
// Then mark the child as runnable and return.
//
// Returns: child's envid to the parent, 0 to the child, < 0 on error.
// It is also OK to panic on error.
//
// Hint:
//   Use uvpd, uvpt, and duppage.
//   Remember to fix "thisenv" in the child process.
//   Neither user exception stack should ever be marked copy-on-write,
//   so you must allocate a new page for the child's user exception stack.
//
envid_t
fork(void)
{
	// LAB 4: Your code here.
	envid_t envid;
	uintptr_t pageVa;
	unsigned pn;
	//设置pgfault情况下对应处理的函数。
	set_pgfault_handler(pgfault);
	//首先需要分配一个env，env_alloc是内核态调用的，因此只能去调系统调用对应的函数来请求分配一个环境描述符。
	envid=sys_exofork();
	//分配成功
	if(envid>0){
		//接下来应该要拷贝父进程。当然这里让我吃惊的是直接可以调用对应的宏并且并没有去请求父进程。jos系统似乎是把所有的子进程都来源于同一进程，而不是父子继承关系。
		for(pageVa=UTEXT;pageVa<USTACKTOP;pageVa+=PGSIZE){
			pn=PGNUM(pageVa);
			//检查权限，挨个copy页
			if((uvpd[PDX(pageVa)]&PTE_P)&&(uvpt[pn]&PTE_P)&&(uvpt[pn]&PTE_U)){
				duppage(envid,pn);}
			}
		//分配异常栈。
		int32_t flag=sys_page_alloc(envid,(void*)(UXSTACKTOP-PGSIZE),PTE_P|PTE_U|PTE_W);
		if(flag<0){
			panic("panic by alloc page");
		}
		//设置对应的异常处理函数。
		extern void _pgfault_upcall(void);
		flag=sys_env_set_pgfault_upcall(envid,_pgfault_upcall);
		if(flag<0){
			panic("panic by set pgfault upcall faction");
		}
		//设置子进程，等待调度。
		flag=sys_env_set_status(envid,ENV_RUNNABLE);
		if(flag<0){
			panic("panic by set env status to ENV_RUNABLE");
		}
		return envid;
		}
	else if(envid==0){
        thisenv = &envs[ENVX(sys_getenvid())];	//在子进程中设置thisenv为自己
		return 0;
		}
	else{
		//否则报错。
		panic("panic by alloc envid");
	}

	
}

// Challenge!
int
sfork(void)
{
	panic("sfork not implemented");
	return -E_INVAL;
}
